using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Text;
using RaisingStudio.SmallBasicCompiler.Expressions;
using RaisingStudio.SmallBasicCompiler.Statements;

using RaisingStudio.SmallProgram.Library;
using System.Diagnostics;

namespace RaisingStudio.SmallBasicCompiler
{
    public class VisualBasicExporter
    {
        private int indentationLevel;
        private TextWriter writer;
        private Parser parser;
        private Compiler compiler;
        private List<string> keywords = new List<string>
		{
			"addhandler", 
			"addressof", 
			"alias", 
			"and", 
			"andalso", 
			"ansi", 
			"as", 
			"assembly", 
			"auto", 
			"boolean", 
			"byref", 
			"byte", 
			"byval", 
			"call", 
			"case", 
			"catch", 
			"cbool", 
			"cbyte", 
			"cchar", 
			"cdate", 
			"cdec", 
			"cdbl", 
			"char", 
			"cint", 
			"class", 
			"clng", 
			"cobj", 
			"const", 
			"cshort", 
			"csng", 
			"cstr", 
			"ctype", 
			"date", 
			"decimal", 
			"declare", 
			"default", 
			"delegate", 
			"dim", 
			"directcast", 
			"do", 
			"double", 
			"each", 
			"else", 
			"elseif", 
			"end", 
			"enum", 
			"erase", 
			"error", 
			"event", 
			"exit", 
			"false", 
			"finally", 
			"for", 
			"friend", 
			"function", 
			"get", 
			"gettype", 
			"gosub", 
			"goto", 
			"handles", 
			"if", 
			"implements", 
			"imports", 
			"in", 
			"inherits", 
			"integer", 
			"interface", 
			"is", 
			"let", 
			"lib", 
			"like", 
			"long", 
			"loop", 
			"main", 
			"me", 
			"mod", 
			"module", 
			"mustinherit", 
			"mustoverride", 
			"mybase", 
			"myclass", 
			"namespace", 
			"new", 
			"next", 
			"not", 
			"nothing", 
			"notinheritable", 
			"notoverridable", 
			"object", 
			"on", 
			"option", 
			"optional", 
			"or", 
			"orelse", 
			"overloads", 
			"overridable", 
			"overrides", 
			"paramarray", 
			"preserve", 
			"private", 
			"property", 
			"protected", 
			"public", 
			"raiseevent", 
			"readonly", 
			"redim", 
			"rem", 
			"removehandler", 
			"resume", 
			"return", 
			"select", 
			"set", 
			"shadows", 
			"shared", 
			"short", 
			"single", 
			"static", 
			"step", 
			"stop", 
			"string", 
			"structure", 
			"sub", 
			"synclock", 
			"then", 
			"throw", 
			"to", 
			"true", 
			"try", 
			"typeof", 
			"unicode", 
			"until", 
			"variant", 
			"when", 
			"while", 
			"with", 
			"withevents", 
			"writeonly", 
			"xor"
		};
        private List<string> conflictingTypes = new List<string>
		{
			"math", 
			"array"
		};

        public VisualBasicExporter(Compiler compiler)
        {
            if (compiler == null)
            {
                throw new ArgumentNullException("compiler");
            }
            this.compiler = compiler;
            this.parser = compiler.Parser;
            if (this.parser.Errors.Count > 0)
            {
                throw new InvalidOperationException();
            }
        }

        public void CreateProgramFile(string filePath, Parser parser, string moduleName)
        {
            using (this.writer = new StreamWriter(filePath))
            {
                WriteProgramCode(parser, moduleName);
            }
        }

        public string GetProgramCode(Parser parser, string moduleName)
        {
            StringBuilder code = new StringBuilder();
            MemoryStream memoryStream = new MemoryStream();
            using (this.writer = new StringWriter(code))
            {
                WriteProgramCode(parser, moduleName);
                this.writer.Flush();
            }
            return code.ToString();
        }

        private void WriteProgramCode(Parser parser, string moduleName)
        {
            this.writer.WriteLine("Module {0}", moduleName);
            this.indentationLevel++;
            this.EmitVariableDeclarations(parser);
            this.Indent();
            this.writer.WriteLine("Sub Main()");
            this.indentationLevel++;
            foreach (Statement current in parser.ParseTree)
            {
                this.EmitStatement(current);
            }
            this.indentationLevel--;
            this.Indent();
            this.writer.WriteLine("End Sub");
            foreach (Statement current in parser.ParseTree)
            {
                SubroutineStatement subroutineStatement = current as SubroutineStatement;
                if (subroutineStatement != null)
                {
                    this.EmitSubroutineStatement(subroutineStatement);
                }
            }
            this.indentationLevel--;
            this.writer.WriteLine("End Module");
        }


        private string ConvertExpressionToVB(Expression expression)
        {
            Type type = expression.GetType();
            if (type == typeof(BinaryExpression))
            {
                return this.ConvertBinaryExpressionToVB(expression as BinaryExpression);
            }
            if (type == typeof(ArrayExpression))
            {
                return this.ConvertArrayExpressionToVB(expression as ArrayExpression);
            }
            if (type == typeof(IdentifierExpression))
            {
                return this.ConvertIdentifierExpressionToVB(expression as IdentifierExpression);
            }
            if (type == typeof(LiteralExpression))
            {
                return this.ConvertLiteralExpressionToVB(expression as LiteralExpression);
            }
            if (type == typeof(MethodCallExpression))
            {
                return this.ConvertMethodCallExpressionToVB(expression as MethodCallExpression);
            }
            if (type == typeof(NegativeExpression))
            {
                return this.ConvertNegativeExpressionToVB(expression as NegativeExpression);
            }
            if (type == typeof(PropertyExpression))
            {
                return this.ConvertPropertyExpressionToVB(expression as PropertyExpression);
            }
            return "";
        }


        private string ConvertAssignmentExpressionToVB(ArrayExpression leftExpression, Expression rightExpression)
        {
            ArrayExpression arrayExpression = leftExpression.LeftHand as ArrayExpression;
            if (arrayExpression != null)
            {
                Stack<string> code = new Stack<string>();
                code.Push(string.Format("Primitive.SetArrayValue({0}, {1}, {2})", ConvertExpressionToVB(rightExpression), ConvertExpressionToVB(arrayExpression), ConvertExpressionToVB(leftExpression.Indexer)));
                ConvertArrayExpressionForSetter(arrayExpression, code);
                return code.Pop();
            }
            else
            {
                return string.Format("{0} = {1}", this.ConvertExpressionToVB(leftExpression), this.ConvertExpressionToVB(rightExpression));
            }
        }

        private void ConvertArrayExpressionForSetter(ArrayExpression arrayExpression, Stack<string> code)
        {
            ArrayExpression leftExpression = arrayExpression.LeftHand as ArrayExpression;
            IdentifierExpression identifierExpression = arrayExpression.LeftHand as IdentifierExpression;
            if (leftExpression != null)
            {
                code.Push(string.Format("Primitive.SetArrayValue({0}, {1}, {2})", code.Pop(), ConvertExpressionToVB(leftExpression), ConvertExpressionToVB(arrayExpression.Indexer)));
                ConvertArrayExpressionForSetter(leftExpression, code);
            }
            if (identifierExpression != null)
            {
                code.Push(string.Format("{0} = Primitive.SetArrayValue({1}, {2}, {3})", ConvertExpressionToVB(identifierExpression), code.Pop(), ConvertExpressionToVB(identifierExpression), ConvertExpressionToVB(arrayExpression.Indexer)));
            }
        }

        private void EmitStatement(Statement statement)
        {
            Type type = statement.GetType();
            if (type == typeof(AssignmentStatement))
            {
                this.EmitAssignmentStatement(statement as AssignmentStatement);
                return;
            }
            if (type == typeof(ElseIfStatement))
            {
                this.EmitElseIfStatement(statement as ElseIfStatement);
                return;
            }
            if (type == typeof(EmptyStatement))
            {
                this.EmitEmptyStatement(statement as EmptyStatement);
                return;
            }
            if (type == typeof(ForStatement))
            {
                this.EmitForStatement(statement as ForStatement);
                return;
            }
            if (type == typeof(GotoStatement))
            {
                this.EmitGotoStatement(statement as GotoStatement);
                return;
            }
            if (type == typeof(IfStatement))
            {
                this.EmitIfStatement(statement as IfStatement);
                return;
            }
            if (type == typeof(LabelStatement))
            {
                this.EmitLabelStatement(statement as LabelStatement);
                return;
            }
            if (type == typeof(MethodCallStatement))
            {
                this.EmitMethodCallStatement(statement as MethodCallStatement);
                return;
            }
            if (type == typeof(SubroutineCallStatement))
            {
                this.EmitSubroutineCallStatement(statement as SubroutineCallStatement);
                return;
            }
            if (type == typeof(WhileStatement))
            {
                this.EmitWhileStatement(statement as WhileStatement);
            }
        }
        private void EmitVariableDeclarations(Parser parser)
        {
            if (parser.SymbolTable.Variables.Count == 0)
            {
                return;
            }
            this.Indent();
            this.writer.Write("Dim ");
            bool flag = true;
            foreach (KeyValuePair<string, TokenInfo> current in parser.SymbolTable.Variables)
            {
                string text = this.NormalizeVariable(current.Value.Text);
                if (flag)
                {
                    this.writer.Write(text);
                    flag = false;
                }
                else
                {
                    this.writer.Write(", " + text);
                }
            }
            this.writer.WriteLine(" As Primitive");
        }

        private void Indent()
        {
            this.writer.Write(new string(' ', this.indentationLevel * 4));
        }

        private string MakeSafe(string name)
        {
            StringBuilder stringBuilder = new StringBuilder();
            for (int i = 0; i < name.Length; i++)
            {
                char c = name[i];
                if (char.IsLetterOrDigit(c) || c == '_')
                {
                    stringBuilder.Append(c);
                }
                else
                {
                    stringBuilder.Append('_');
                }
            }
            return stringBuilder.ToString();
        }

        private string NormalizeTypeName(string typeName)
        {
            if (this.conflictingTypes.Contains(typeName.ToLowerInvariant()))
            {
                //return "RaisingStudio.SmallProgram.Library." + typeName;
                return typeName;
            }
            return typeName;
        }
        private string NormalizeVariable(string variableName)
        {
            if (this.keywords.Contains(variableName.ToLowerInvariant()))
            {
                return "__" + variableName;
            }
            return variableName;
        }

        private string ConvertArrayExpressionToVB(ArrayExpression arrayExpression)
        {
            return string.Format("{0}({1})", this.ConvertExpressionToVB(arrayExpression.LeftHand), this.ConvertExpressionToVB(arrayExpression.Indexer));
        }
        private string ConvertBinaryExpressionToVB(BinaryExpression binaryExpression)
        {
            string text = this.ConvertExpressionToVB(binaryExpression.RightHandSide);
            LiteralExpression literalExpression = binaryExpression.RightHandSide as LiteralExpression;
            int operatorPriority = Parser.GetOperatorPriority(binaryExpression.Operator.Token);
            if (operatorPriority < 7 && literalExpression != null && literalExpression.Literal.Token == Token.StringLiteral)
            {
                return string.Format("{0} {1} CType({2}, Primitive)", this.ConvertExpressionToVB(binaryExpression.LeftHandSide), binaryExpression.Operator.Text, text);
            }
            StringBuilder stringBuilder = new StringBuilder();
            if (operatorPriority < binaryExpression.LeftHandSide.Precedence && binaryExpression.LeftHandSide is BinaryExpression)
            {
                stringBuilder.AppendFormat("({0})", this.ConvertExpressionToVB(binaryExpression.LeftHandSide));
            }
            else
            {
                stringBuilder.AppendFormat("{0}", this.ConvertExpressionToVB(binaryExpression.LeftHandSide));
            }
            stringBuilder.AppendFormat(" {0} ", binaryExpression.Operator.Text);
            if (operatorPriority < binaryExpression.RightHandSide.Precedence && binaryExpression.RightHandSide is BinaryExpression)
            {
                stringBuilder.AppendFormat("({0})", text);
            }
            else
            {
                stringBuilder.AppendFormat("{0}", text);
            }
            return stringBuilder.ToString();
        }
        private string ConvertIdentifierExpressionToVB(IdentifierExpression identifierExpression)
        {
            return this.NormalizeVariable(identifierExpression.Identifier.Text);
        }
        private string ConvertLiteralExpressionToVB(LiteralExpression literalExpression)
        {
            if (literalExpression.Literal.NormalizedText == "\"true\"")
            {
                return "true";
            }
            if (literalExpression.Literal.NormalizedText == "\"false\"")
            {
                return "false";
            }
            if (literalExpression.Literal.NormalizedText.StartsWith("\"") && !literalExpression.Literal.NormalizedText.EndsWith("\""))
            {
                return literalExpression.Literal.NormalizedText + "\"";
            }
            return literalExpression.Literal.Text;
        }
        private string ConvertMethodCallExpressionToVB(MethodCallExpression methodCallExpression)
        {
            StringBuilder stringBuilder = new StringBuilder();
            stringBuilder.AppendFormat("{0}.{1}(", this.NormalizeTypeName(methodCallExpression.TypeName.Text), methodCallExpression.MethodName.Text);
            bool flag = true;
            foreach (Expression current in methodCallExpression.Arguments)
            {
                if (flag)
                {
                    flag = false;
                    stringBuilder.Append(this.ConvertExpressionToVB(current));
                }
                else
                {
                    stringBuilder.AppendFormat(", {0}", this.ConvertExpressionToVB(current));
                }
            }
            stringBuilder.Append(")");
            return stringBuilder.ToString();
        }
        private string ConvertNegativeExpressionToVB(NegativeExpression negativeExpression)
        {
            return "-" + this.ConvertExpressionToVB(negativeExpression.Expression);
        }
        private string ConvertPropertyExpressionToVB(PropertyExpression propertyExpression)
        {
            return string.Format("{0}.{1}", this.NormalizeTypeName(propertyExpression.TypeName.Text), propertyExpression.PropertyName.Text);
        }

        private void EmitAssignmentStatement(AssignmentStatement assignmentStatement)
        {
            this.Indent();
            PropertyExpression propertyExpression = assignmentStatement.LeftValue as PropertyExpression;
            if (propertyExpression != null)
            {
                TypeInfo typeInfo = this.compiler.TypeInfoBag.Types[propertyExpression.TypeName.NormalizedText];
                EventInfo eventInfo;
                if (typeInfo.Events.TryGetValue(propertyExpression.PropertyName.NormalizedText, out eventInfo))
                {
                    this.writer.WriteLine("AddHandler {0}, AddressOf {1}", this.ConvertExpressionToVB(propertyExpression), this.ConvertExpressionToVB(assignmentStatement.RightValue));
                    return;
                }
            }
            ArrayExpression arrayExpression = assignmentStatement.LeftValue as ArrayExpression;
            if (arrayExpression != null)
            {
                this.writer.Write(ConvertAssignmentExpressionToVB(arrayExpression, assignmentStatement.RightValue));
            }
            else
            {
                this.writer.Write("{0} = {1}", this.ConvertExpressionToVB(assignmentStatement.LeftValue), this.ConvertExpressionToVB(assignmentStatement.RightValue));
                if (assignmentStatement.EndingComment.Token != Token.Illegal)
                {
                    this.writer.Write(" {0}", assignmentStatement.EndingComment.Text);
                }
            }
            this.writer.WriteLine();
        }
        private void EmitElseIfStatement(ElseIfStatement elseIfStatement)
        {
            this.Indent();
            this.writer.Write("ElseIf {0} Then", this.ConvertExpressionToVB(elseIfStatement.Condition));
            if (elseIfStatement.EndingComment.Token != Token.Illegal)
            {
                this.writer.Write(" {0}", elseIfStatement.EndingComment.Text);
            }
            this.writer.WriteLine();
            this.indentationLevel++;
            foreach (Statement current in elseIfStatement.ThenStatements)
            {
                this.EmitStatement(current);
            }
            this.indentationLevel--;
        }
        private void EmitEmptyStatement(EmptyStatement emptyStatement)
        {
            if (emptyStatement.EndingComment.Token != Token.Illegal)
            {
                this.Indent();
                this.writer.WriteLine(emptyStatement.EndingComment.Text);
                return;
            }
            this.writer.WriteLine();
        }
        private void EmitForStatement(ForStatement forStatement)
        {
            this.Indent();
            this.writer.Write("For {0} = {1} To {2}", forStatement.Iterator.Text, this.ConvertExpressionToVB(forStatement.InitialValue), this.ConvertExpressionToVB(forStatement.FinalValue));
            if (forStatement.StepToken.TokenType != TokenType.Illegal)
            {
                this.writer.Write(" Step {0}", this.ConvertExpressionToVB(forStatement.StepValue));
            }
            if (forStatement.EndingComment.Token != Token.Illegal)
            {
                this.writer.Write(" {0}", forStatement.EndingComment.Text);
            }
            this.writer.WriteLine();
            this.indentationLevel++;
            foreach (Statement current in forStatement.ForBody)
            {
                this.EmitStatement(current);
            }
            this.indentationLevel--;
            this.Indent();
            this.writer.WriteLine("Next");
        }
        private void EmitGotoStatement(GotoStatement gotoStatement)
        {
            this.Indent();
            this.writer.Write("Goto {0}", gotoStatement.Label.Text);
            if (gotoStatement.EndingComment.Token != Token.Illegal)
            {
                this.writer.Write(" {0}", gotoStatement.EndingComment.Text);
            }
            this.writer.WriteLine();
        }
        private void EmitIfStatement(IfStatement ifStatement)
        {
            this.Indent();
            this.writer.Write("If {0} Then", this.ConvertExpressionToVB(ifStatement.Condition));
            if (ifStatement.EndingComment.Token != Token.Illegal)
            {
                this.writer.Write(" {0}", ifStatement.EndingComment.Text);
            }
            this.writer.WriteLine();
            this.indentationLevel++;
            foreach (Statement current in ifStatement.ThenStatements)
            {
                this.EmitStatement(current);
            }
            this.indentationLevel--;
            foreach (Statement current in ifStatement.ElseIfStatements)
            {
                this.EmitStatement(current);
            }
            if (ifStatement.ElseStatements.Count > 0)
            {
                this.Indent();
                this.writer.WriteLine("Else");
                this.indentationLevel++;
                foreach (Statement current in ifStatement.ElseStatements)
                {
                    this.EmitStatement(current);
                }
                this.indentationLevel--;
            }
            this.Indent();
            this.writer.WriteLine("End If");
        }
        private void EmitLabelStatement(LabelStatement labelStatement)
        {
            this.writer.Write("{0}:", labelStatement.LabelToken.Text);
            if (labelStatement.EndingComment.Token != Token.Illegal)
            {
                this.writer.Write(" {0}", labelStatement.EndingComment.Text);
            }
            this.writer.WriteLine();
        }
        private void EmitMethodCallStatement(MethodCallStatement methodCallStatement)
        {
            this.Indent();
            this.writer.Write("{0}", this.ConvertExpressionToVB(methodCallStatement.MethodCallExpression));
            if (methodCallStatement.EndingComment.Token != Token.Illegal)
            {
                this.writer.Write(" {0}", methodCallStatement.EndingComment.Text);
            }
            this.writer.WriteLine();
        }
        private void EmitSubroutineCallStatement(SubroutineCallStatement subroutineCallStatement)
        {
            this.Indent();
            this.writer.Write("{0}()", this.NormalizeVariable(subroutineCallStatement.SubroutineName.Text));
            if (subroutineCallStatement.EndingComment.Token != Token.Illegal)
            {
                this.writer.Write(" {0}", subroutineCallStatement.EndingComment.Text);
            }
            this.writer.WriteLine();
        }
        private void EmitSubroutineStatement(SubroutineStatement subroutineStatement)
        {
            this.Indent();
            this.writer.Write("Sub {0}()", this.NormalizeVariable(subroutineStatement.SubroutineName.Text));
            if (subroutineStatement.EndingComment.Token != Token.Illegal)
            {
                this.writer.Write(" {0}", subroutineStatement.EndingComment.Text);
            }
            this.writer.WriteLine();
            this.indentationLevel++;
            foreach (Statement current in subroutineStatement.SubroutineBody)
            {
                this.EmitStatement(current);
            }
            this.indentationLevel--;
            this.Indent();
            this.writer.WriteLine("End Sub");
        }
        private void EmitWhileStatement(WhileStatement whileStatement)
        {
            this.Indent();
            this.writer.Write("While {0}", this.ConvertExpressionToVB(whileStatement.Condition));
            if (whileStatement.EndingComment.Token != Token.Illegal)
            {
                this.writer.Write(" {0}", whileStatement.EndingComment.Text);
            }
            this.writer.WriteLine();
            this.indentationLevel++;
            foreach (Statement current in whileStatement.WhileBody)
            {
                this.EmitStatement(current);
            }
            this.indentationLevel--;
            this.Indent();
            this.writer.WriteLine("End While");
        }
    }
}
